<?php
/**
 * Класс обеспечивает соединение с базой данных
 * Автоматически формирует массивы
 * Обеспечивает обработку исключительных ситуаций при осуществлении запросов
 *
 * @author Дмитрий Кузьма <kouzma.d@gmail.com>
 * @version 1.0
 */

class Service_Database {
  private $hConn;   //Дескриптор соединения с базой данных
  public static $objDB;
  /**
   * Конструктор - устанавливаем соединение с БД.
   *
   */
  private function __construct() {
    //Service_Debug::setDebug();
    $hostName  = DB_HOST . ":" . DB_PORT;

    $this->hConn = @mysql_connect($hostName, DB_USER, DB_PASSWORD);
    if (! is_resource($this->hConn)) {
      throw new Exception('Unable to connect to the database ' .
                         'Host: "' . $hostName . '"; User: "' . DB_USER. '"', E_USER_ERROR);
    }
    $curBD = @mysql_select_db(DB_NAME);//Выбираем базу данных для работы
    if (! ($curBD)) {
      throw new Exception('Unable to select database ' .
                          'Database: "' . DB_NAME . '"', E_USER_ERROR);
    }
    @mysql_query("SET NAMES 'utf8'");
  }

  /**
   * Функция обеспечивает работу шаблона Singleton.
   * Благодаря ей у нас всегда будет только одно подключение к базе
   *
   * @return resource
   */
  static public function instance() {
  	//Service_Debug::setDebug();
    if (!isset(self::$objDB)) {
      self::$objDB = new Service_Database();
    }
    return self::$objDB;
  }

  /**
   * Получает информацию о последнем вставленном идентификаторе
   *
   * @return integer - значение последнего вставленно идентификатора (false, если не было удачного запроса; 0 если в таблице нет autoincrement)
   */
  public function getLastInsertId() {
  	Service_Debug::setDebug();
    $stmt = "SELECT LAST_INSERT_ID() AS LID";
    $hRes = mysql_query($stmt, $this->hConn);
    if (! $hRes) {//Проверяем, корректно ливыполнен запрос
      $err = mysql_error($this->hConn) . $stmt;
      throw new Exception($err);
    }
    $arrLID = mysql_fetch_assoc($hRes);
    return $arrLID['LID'];
  }
  /**
   * Вставляет в определенную таблицу массив значений
   *
   * @param string $table - название таблицы
   * @param array $arFieldValues - массив вставляемых значений (ключ соответствует полю в БД)
   * @param bool $ignore - вставлять ли IGNORE
   * @return integer - количество вставленных строк
   */
  public function insert($table, $arFieldValues, $ignore = false) {
  	Service_Debug::setDebug();
    $fields = array_keys($arFieldValues);     //Массив названий элементов таблицы
    $values = array_values($arFieldValues);   //Массив значений
		for ($i = 0; $i < sizeof($fields); $i++) {
		  $fields[$i] = '`' . $fields[$i] . '`';
		}

    $escVals = array();   //Выходной массив значений
    foreach($values as $val) {  //Проходим массив значений поэлементно
      //if (! is_numeric($val)) {  //Если текущее значение - не число
        if ($val === null)
          $val = 'null';
        else
          $val = "'" . $val . "'";//Приводим к корректному выводу
      //}
      $escVals[] = $val;  //Записываем корректное значение в массив
    }
    //Генерируем SQL запрос
    $stmt = " INSERT " . ($ignore ? "IGNORE " : "") . "INTO `". $table . "` ( ";
    	$stmt .= join(", ", $fields);
    $stmt .= ' ) VALUES( ';
    	$stmt .= join(', ', $escVals);
    $stmt .= ' )';
    $hRes = @mysql_query($stmt, $this->hConn) or die (mysql_error());
    if (! $hRes) {  //Если не получен положительный результат о вставке
      $err = mysql_error($this->hConn) . "\n" . $stmt;
      throw new Exception($err);
    }
    return mysql_affected_rows($this->hConn);
  }

  /**
   * Вставляет в определенную таблицу массив значений
   *
   * @param string $table - название таблицы
   * @param array $arFieldValues - массив вставляемых значений (ключ соответствует полю в БД)
   * @return integer - идентификатор вставленого элемента
   */
  public function insertAutoincrement($table, $arFieldValues) {
  	Service_Debug::setDebug();
    if ( $this->insert($table, $arFieldValues) ) return $this->getLastInsertId();
    else return false;
  }
/**
   * Выполняет запрос на вставку значений
   *
   * @param string $stmt - строка запроса
   * @return integer - идентификатор
   */
  public function queryInsertAutoincrement($stmt) {
  	Service_Debug::setDebug();
    if ( $this->query($stmt) > 0) return $this->getLastInsertId();
    else return false;
  }

  /**
   * Удаляет из таблицы все элементы, удовлетворяющие условию
   *
   * @param string $table - название таблицы
   * @param array $arConditions - массив условий (ключ соответствует полю в БД)
   * @return integer - количество удаленных строк
   */
  function delete($table, $arConditions) {
  	Service_Debug::setDebug();
    //Создаем строку WHERE
    $arWhere = array();//Массив элементов для WHERE
    foreach($arConditions as $field => $val) {  //Проходим массив значений поэлементно
      if ($val === null) {
      	$arWhere[] = $field . " IS NULL" ;//Записываем корректное значение в массив
      }
      else {
		  	//if (! is_numeric($val)) {  //Если текущее значение - не число
		      //$val = "'" . absolutelyEscapeString($val) . "'";//Приводим к корректному выводу
		      $val = "'" . $val . "'";//Приводим к корректному выводу
		    //}
		    $arWhere[] = $field . " = " . $val;//Записываем корректное значение в массив
      }
    }
    //Формируем запрос на удаление
    $stmt = "DELETE FROM " . $table . " WHERE " . join(' AND ', $arWhere);
    $hRes = @mysql_query($stmt, $this->hConn) or die ("Impossible to request removal" . $stmt . $this->hConn);
    if (! $hRes) {  //Если не получен положительный результат о вставке
      $err = mysql_error($this->hConn) . $stmt;
      throw new Exception($err);
    }

    return mysql_affected_rows($this->hConn);
  }

  /**
   * выполняет любой запрос SQL типа SELECT
   *
   * @param string $stmt - строка запроса
   * @return array - двумерный ассоциативный массив
   */
  public function select($stmt) {
  	Service_Debug::setDebug();  	
    $hRes = @mysql_query($stmt, $this->hConn);
    if (! $hRes) {//Проверяем, корректно ливыполнен запрос
      $err = mysql_error($this->hConn) . $stmt;
      print_r($err);
      throw new Exception($err);
    }
    $arReturn = array();//Массив для возвращения функцией (результат)
    while($row = mysql_fetch_assoc($hRes)) {
      $arReturn[] = $row;
    }
    Service_Debug::log('DB select stmt',$stmt);
    return $arReturn;
  }

  /**
   * выполняет любой запрос SQL типа SELECT для получения информации об одном поле и возвращает простой одномерный массив
   *
   * @param string $stmt - строка запроса
   * @return array - одномерный индексный массив
   */
  public function selectSimpleArray($stmt) {
  	Service_Debug::setDebug();
    $hRes = @mysql_query($stmt, $this->hConn);

    if (! $hRes) {//Проверяем, корректно ливыполнен запрос
      $err = mysql_error($this->hConn) . $stmt;
      throw new Exception($err);
    }
    if ( mysql_num_rows($hRes) == 0 ) return array();
    $arReturn = array();//Массив для возвращения функцией (результат)
    while($row = mysql_fetch_array($hRes)) {
      $arReturn[] = $row[0];
    }
    return $arReturn;
  }
  /**
   * выполняет любой запрос SQL типа SELECT для получения информации об одном поле одной строки
   *
   * @param string $stmt - строка запроса
   * @return mixed - значение единственного поля
   */

  public function selectSimpleField($stmt) {
  	Service_Debug::setDebug();
    $hRes = @mysql_query($stmt, $this->hConn);
    
    if (! $hRes) {//Проверяем, корректно ливыполнен запрос
      $err = mysql_error($this->hConn) . $stmt;
      throw new Exception($err);
    }
    if ( mysql_num_rows($hRes) == 0 ) return null;
    $arTemp =  mysql_fetch_array($hRes);
    return $arTemp[0];
  }

  /**
   * выполняет любой запрос SQL типа SELECT, возвращает только первый элемент
   *
   * @param string $stmt - строка запроса
   * @return array - двумерный ассоциативный массив
   */
  public function selectOne($stmt) {
  	Service_Debug::setDebug();
    $hRes = @mysql_query($stmt, $this->hConn);
    if (! $hRes) {//Проверяем, корректно ливыполнен запрос
      $err = mysql_error($this->hConn) . $stmt;
      throw new Exception($err);
    }
    if ( mysql_num_rows($hRes) == 0 ) return array();
    return mysql_fetch_assoc($hRes);
  }

  /**
   * Выполняет произвольный SQL запрос
   *
   * @param string $stmt - строка запроса
   * @return integer - количество обработанных строк
   */
  public function query($stmt) {
  	Service_Debug::setDebug();
    $hRes = @mysql_query($stmt, $this->hConn);
    if (! $hRes) {//Проверяем, корректно ливыполнен запрос
      $err = mysql_error($this->hConn) . ' ' . $stmt;
      print_r($err);
      throw new Exception($err);
    }
    return @mysql_affected_rows($this->hConn);
  }

  //Функция обновляет в таблице $table поля $arFieldValues, но только те элементы, которые соотвествуют условию $arConditions
  /**
   * Обновляет в таблице определенные поля, согласно условию
   *
   * @param string $table - название таблицы
   * @param array $arFieldValues - массив обновляемых значений (ключ соответствует полю в БД)
   * @param array $arConditions - массив условий (ключ соответствует полю в БД)
   * @return integer - количество обновленных строк
   */
  public function update($table, $arFieldValues, $arConditions) {  	
  	Service_Debug::setDebug();
    // Формируем корректный массив значений
    $arUpdates = array();//Массив элементов для значений
    foreach($arFieldValues as $field => $val) {//Проходим массив значений поэлементно
    	//if (! is_numeric($val)) { //Если текущее значение - не число
        if ($val === null) $val = 'null';
        else $val = "'" . $val . "'";//Приводим к корректному выводу
      //}
      $arUpdates[] = "`" . $field . "` = " . $val;//Записываем корректное значение в массив
    }

    // Формируем корректный массив WHERE
    $arWhere = array();//Массив элементов для WHERE
    foreach($arConditions as $field => $val) {//Проходим массив WHERE поэлементно
      if ($val === null) {
      	$arWhere[] = "`" . $field . "` IS NULL" ;//Записываем корректное значение в массив
      }
      else {
      	if (! is_numeric($val)) { //Если текущее значение - не число
	        $val = "'" . $val . "'";//Приводим к корректному выводу
      	}
      	$arWhere[] = "`" . $field . "` = " . $val;//Записываем корректное значение в массив
      }
    }
    //Формируем SQL-запрос на обновление данных
    $stmt  = "UPDATE `" . $table . "` SET ";
    $stmt .= join(', ', $arUpdates);
    $stmt .= ' WHERE ' . join(' AND ', $arWhere);
    //echo $stmt;
    $hRes = mysql_query($stmt, $this->hConn);//Выполняем запрос
    if (! $hRes) {//Проверяем запрос на корректность
      $err = mysql_error($this->hConn) . $stmt;
      print_r($err);
      throw new Exception($err);
    }
    return mysql_affected_rows($this->hConn);
  }

  /**
   * Деструктор - закрываем соединение с базой данных
   *
   */
  public function __destruct() {
  	Service_Debug::setDebug();
    if (is_resource($this->hConn)) {
      @mysql_close($this->hConn);
    }
  }
  /**
   * Проеряет, существует ли запись в БД
   *
   * @param string $stmt
   * @return bool
   */
  public function isRecordExist($stmt) {
  	Service_Debug::setDebug();
    $hRes = @mysql_query($stmt, $this->hConn);

    if (! $hRes) {//Проверяем, корректно ливыполнен запрос
      $err = mysql_error($this->hConn) . $stmt;
      throw new Exception($err);
    }
    if ( mysql_num_rows($hRes) > 0 ) return true;
    else return false;
  }

  /**
   * Начинает транзакцию
   *
   */
  public function startTransaction() {
  	$this->query("SET AUTOCOMMIT=0;");
  	$this->query("START TRANSACTION;");
  }
  
  /**
   * Завершает транзакцию
   *
   */
  public function stopTransaction() {
  	$this->query("COMMIT;");
  	$this->query("SET AUTOCOMMIT=1;");
  }

  /**
   * Отменяет транзакцию
   *
   */
  public function cancelTransaction() {
  	$this->query("ROLLBACK;");
  }

  public function searchTable($search, $break = false) {
  	Service_Debug::setDebug();
  	$stmt = "SHOW TABLES;";
	  $arrTables = $this->select($stmt);
	  foreach ($arrTables as $table) {
			$table = $table['Tables_in_hotels'];
		  $stmt = "DESCRIBE " . $table;
		  $arrFields = $this->select($stmt);
		  foreach ($arrFields as $arrField) {
		  	$stmt = "SELECT COUNT(*) FROM  " . $table . " WHERE " . $arrField['Field'] . " LIKE '" . $search . "'";
		  	if ($this->selectSimpleField($stmt)) {
		  		echo '<b>Table:</b> ' . $table . ' <b>Field:</b> ' . $arrField['Field'] . "<br />";
		  		if ($break) return !1;
		  	}
		  }
	  }
  }
  
  /**
   * Формирует INSERT запрос для вставки нескольких значений 
   * 
   * @param string $table - Название таблицы для вставки
   * @param string $essenceType - Название поля, которое принимает разные значения 
   * @param array $arrEssenceIds - Массив значений поля
   * @param array $arrOtherFields - Ассоциативный массив с остальными полями и значениями (Sample: array('ID_hotel' => 15, 'ID_room' => 12))
   */
  public static function makeMultipleInsertString($table, $essenceType, $arrEssenceIds, $arrOtherFields) {
  	Service_Debug::setDebug();
  	if (empty($arrEssenceIds)) return false;
  	if (empty($arrOtherFields)) return false;
  	$i = 0;
		$str = '';
		foreach ($arrEssenceIds as $essenceId) {
			if ($i++) $str .= ',';
			$str .= "('" . $essenceId . "', '" . join("','", array_values($arrOtherFields)) . "')";
		}  	
		$stmt = "INSERT INTO " . $table . " (" . $essenceType . ", " . join(",", array_keys($arrOtherFields)) . ") VALUES " . $str;
		return $stmt;	
  }
  
}
?>